
import time
import struct
from micropython import const

_SOFTRESET = const(0xFE)
_TEMPERATURE = const(0xF3)
_HUMIDITY = const(0xF5)
_READ_USER1 = const(0xE7)
_WRITE_USER1 = const(0xE6)
_TEMP_RH_RES = (0, 1, 128, 129)


class HTU21DF:
    def __init__(self, i2c, address: int = 0x40) -> None:
        self._i2c = i2c
        self._address = address

    def reset(self) -> None:
        self._i2c.writeto(self._address, bytes([_SOFTRESET]), False)
        time.sleep(0.015)

    @property
    def temperature(self) -> float:
        self._i2c.writeto(self._address, bytes([_TEMPERATURE]), False)
        data = bytearray(3)
        while True:
            # While busy, the sensor doesn't respond to reads.
            try:
                self._i2c.readfrom_into(self._address, data)
                if data[0] != 0xFF:  # Check if read succeeded.
                    break
            except OSError:
                pass
        value, checksum = struct.unpack(">HB", data)

        if checksum != self._crc(data[:2]):
            raise ValueError("CRC mismatch")

        time.sleep(0.050)

        return value * 175.72 / 65536.0 - 46.85

    @property
    def humidity(self) -> float:
        self._i2c.writeto(self._address, bytes([_HUMIDITY]), False)
        data = bytearray(3)
        while True:
            try:
                self._i2c.readfrom_into(self._address, data)
                if data[0] != 0xFF:
                    break
            except OSError:
                pass
        value, checksum = struct.unpack(">HB", data)

        if checksum != self._crc(data[:2]):
            raise ValueError("CRC mismatch")

        time.sleep(0.016)

        return value * 125.0 / 65536.0 - 6.0

    @property
    def temp_rh_resolution(self) -> str:
        data = bytearray(1)
        values = {
            0x00: "RH=0.04, T=0.01",
            0x01: "RH=0.7, T=0.04",
            0x80: "RH=0.17, T=0.02",
            0x81: "RH=0.08, T=0.08",
        }

        self._i2c.writeto(self._address, bytes([_READ_USER1]), False)
        self._i2c.readfrom_into(self._address, data)
        res = data[0] & 0x81

        return values[res]

    @temp_rh_resolution.setter
    def temp_rh_resolution(self, val: int) -> None:
        if val not in _TEMP_RH_RES:
            raise ValueError("Temp/Humidity Resolution must be a valid option")
        data = bytearray(1)
        self._i2c.writeto(self._address, bytes([_READ_USER1]), False)
        self._i2c.readfrom_into(self._address, data)
        reg = data[0] & ~0x81
        reg |= val
        self._i2c.writeto(self._address, bytes([_WRITE_USER1, reg]), False)

    @staticmethod
    def _crc(data: bytearray) -> int:
        crc = 0
        for byte in data:
            crc ^= byte
            for _ in range(8):
                if crc & 0x80:
                    crc <<= 1
                    crc ^= 0x131
                else:
                    crc <<= 1
        return crc
